package main

import (
	json2 "encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

type RequestBuild struct {
	request *Request
}

type Request struct {
	HttpRequest   *http.Request
	Method        string
	Url           string
	Body          string
	Params        map[string]string
	Headers       map[string]string
	AllowRedirect bool
}

type Response struct {
	HttpResponse   *http.Response
	ResponseWriter http.ResponseWriter
	Body           string
	Headers        map[string]string
	Status         int
}

func NewRequest() *RequestBuild {
	return &RequestBuild{request: &Request{Headers: make(map[string]string), Params: make(map[string]string)}}
}

func (rb *RequestBuild) HttpRequest(request *http.Request) *RequestBuild {
	defer request.Body.Close()
	body, _ := ioutil.ReadAll(request.Body)

	header := make(map[string]string)
	for k := range request.Header {
		v := request.Header.Get(k)
		header[k] = v
	}

	rb.request.HttpRequest = request
	rb.request.Url = request.URL.RequestURI()
	rb.request.Method = request.Method
	rb.request.Headers = header
	rb.request.Body = string(body)
	return rb
}

func (rb *RequestBuild) Url(url string) *RequestBuild {
	rb.request.Url = url
	return rb
}

func (rb *RequestBuild) Method(method string) *RequestBuild {
	rb.request.Method = method
	return rb
}

func (rb *RequestBuild) Get(url string) *RequestBuild {
	rb.request.Method = "GET"
	rb.request.Url = url
	return rb
}

func (rb *RequestBuild) Post(url string) *RequestBuild {
	rb.request.Method = "POST"
	rb.request.Url = url
	return rb
}

func (rb *RequestBuild) Body(body string) *RequestBuild {
	rb.request.Body = body
	return rb
}

func (rb *RequestBuild) Param(key string, value string) *RequestBuild {
	rb.request.Params[key] = value
	return rb
}

func (rb *RequestBuild) Params(params map[string]string) *RequestBuild {
	rb.request.Params = params
	return rb
}

func (rb *RequestBuild) Header(key string, value string) *RequestBuild {
	rb.request.Headers[key] = value
	return rb
}

func (rb *RequestBuild) Headers(headers map[string]string) *RequestBuild {
	rb.request.Headers = headers
	return rb
}

func (rb *RequestBuild) AllowRedirect(allow bool) *Request {
	rb.request.AllowRedirect = allow
	return rb.request
}

func (rb *RequestBuild) GetRequest() *Request {
	return rb.request
}

func (rb *RequestBuild) Send() (*Response, error) {
	resp := &Response{}
	response, err := rb.SendRequest(rb.request)
	if err != nil {
		return nil, err
	}

	header := make(map[string]string)
	for k := range response.Header {
		v := response.Header.Get(k)
		header[k] = v
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	resp.HttpResponse = response
	resp.Headers = header
	resp.Body = string(body)
	resp.Status = response.StatusCode
	return resp, nil
}

func (rb *RequestBuild) SendAndReadToJson() (*Json, error) {
	res, err := rb.Send()
	if err != nil {
		return nil, err
	}
	return NewJson(res.Body)
}

func (rb *RequestBuild) SendRequest(req *Request) (*http.Response, error) {
	var (
		resp *http.Response
	)

	params := ""
	if req.Params != nil {
		for k, v := range req.Params {
			params = fmt.Sprintf("%s&%s=%s", params, k, v)
		}
	}

	if req.Method == "POST" && req.Body != "" {
		params = req.Body
		if _, ok := req.Headers["Content-Type"]; !ok {
			req.Headers["Content-Type"] = "application/json"
		}
		if _, ok := req.Headers["Accept"]; !ok {
			req.Headers["Accept"] = "application/json"
		}
	} else if req.Method == "POST" {
		if _, ok := req.Headers["Content-Type"]; !ok {
			req.Headers["Content-Type"] = "application/x-www-form-urlencoded"
		}
	}

	request, err := http.NewRequest(req.Method, req.Url, strings.NewReader(params))
	if err != nil {
		return resp, err
	}

	log.Println(request.Method, request.URL)
	for k, v := range req.Headers {
		request.Header.Set(k, v)
	}

	if req.AllowRedirect {
		c := http.Transport{}
		resp, err = c.RoundTrip(request)
	} else {
		c := http.Client{}
		resp, err = c.Do(request)
	}

	return resp, err
}

type Json struct {
	jsonMap  map[string]interface{}
	_jsonMap map[string]interface{}
}

func NewJsonMap(m map[string]interface{}) *Json {
	json := &Json{jsonMap: m, _jsonMap: m}
	return json
}

func NewJson(jsonStr string) (*Json, error) {
	m := make(map[string]interface{})
	err := json2.Unmarshal([]byte(jsonStr), &m)
	if err != nil {
		return nil, err
	}
	json := &Json{jsonMap: m, _jsonMap: m}
	return json, nil
}

func (json *Json) Get(key string) interface{} {
	keys := strings.Split(key, ".")
	for i, k := range keys {

		if i == len(keys)-1 {
			if value, ok := json.jsonMap[k]; ok {
				json.jsonMap = json._jsonMap
				return value
			}
		}

		kk := k
		if strings.Contains(k, "[") {
			kk = k[:strings.Index(k, "[")]
		}

		value, ok := json.jsonMap[kk]
		if !ok || value == nil {
			json.jsonMap = json._jsonMap
			return ""
		}

		mt := reflect.TypeOf(value)
		if strings.HasPrefix(mt.String(), "map[string]interface ") {
			json.jsonMap = json.jsonMap[kk].(map[string]interface{})
		} else if strings.HasPrefix(mt.String(), "[]interface ") {
			array := json.jsonMap[kk].([]interface{})
			index := 0
			if strings.Contains(k, "[") && strings.Contains(k, "]") {
				indexStr := k[strings.Index(k, "[")+1 : strings.Index(k, "]")]
				index, _ = strconv.Atoi(indexStr)
			}
			if len(array) > index {
				json.jsonMap = array[index].(map[string]interface{})
			} else {
				json.jsonMap = json._jsonMap
				return ""
			}
		} else {
			json.jsonMap = json._jsonMap
			return ""
		}
	}
	json.jsonMap = json._jsonMap
	return ""
}
